<?php
define( "ROUTER_DEFAULT_DOMAIN", 'http://[^/]+' );
abstract class RouterRule
{/*{{{*/
	protected function checkCallable($controller, $action)
	{/*{{{*/
		if (false == is_callable(array(Controller::buildClass($controller), $action)) &&
		    false == is_callable(array(Controller::buildClass($controller), '__call'))) {
			throw new RouterException('无法调用的controll/action组合('.$controller.'/'.$action.')');
		}
	}/*}}}*/
}/*}}}*/

class FixedRouterRule extends RouterRule
{
	private $domain = ROUTER_DEFAULT_DOMAIN;
	public $pattern;
	private $postfix;
	private $urlfor_template;
	private $templates;
	
	public static function create() {
		return new FixedRouterRule();
	}
	public function __construct() {
		$this->templates = array();
	}
	public function domain($domain) {
		$this->domain = $domain;
		return $this;
	}
	public function url($url) {
		$group = null;
		if (preg_match('|^/<controller>/<action>\.?(.*?)$|i', $url, $group)) {
			$this->postfix = $group[1];
		} else {
			throw new RouterRuleException("FixRouterRule : error url ==> $url");
		}
		return $this;
	}
	public function templates($name, $templatepath) {
		$this->templates[$name] = $templatepath;
		return $this;
	}
	public function addto($router) {
		$router->addRule($this);
		return $this;
	}
	public function getIndex() {
		return "*";
	}
	public function compile() {
		$postfix = '';
		if (empty($this->postfix) == false) {
			$postfix = "(?:\\.{$this->postfix})?";
			$this->urlfor_template = "/:controller.{$this->postfix}";
		} else {
			$this->urlfor_template = "/:controller";
		}
		if ($this->domain != ROUTER_DEFAULT_DOMAIN)
			$this->urlfor_template = $this->domain.$this->urlfor_template;
		$this->pattern = "|^{$this->domain}/(?P<controller>[^/]+)/(?P<action>[^/\\.]+)$postfix$|i";
    	
		return $this;
	}
	public function dispatch($context, $url) {
		$group = null;
		if (preg_match($this->pattern, $url, $group)) {
			$context->controller = $group['controller'];
			$context->action     = $group['action'];
			$this->checkCallable($context->controller, $context->action);
			$context->templates['success'] = isset($this->templates['success']) ? $this->templates['success'] : "/{$context->controller}/{$context->action}";
			$context->templates['error'] = isset($this->templates['error']) ? $this->templates['error'] : '/default/error';
			return true;
		}
		return false;
	}
	public function urlfor($controller, $params, $oCharset, $iCharset) {
		$url = str_replace(':controller', $controller, $this->urlfor_template);
		$paramUrl = '';
		if (empty($params) == false) {
			$paramUrl = "?";
			foreach ($params as $key => $value) {
				$paramUrl .= "$key=".urlencode($value)."&";
			}
			$paramUrl = substr($paramUrl, 0, -1);
		}
		return mb_convert_encoding($url.$paramUrl, $oCharset, $iCharset);
	}
}
class NamedRouterRule extends RouterRule {
	public $controller;
	public $action;
	public $paramNames = array();
	public $templates;
	
	public $default_domain = ROUTER_DEFAULT_DOMAIN;
	public $pattern;
	private $experssions;
	private $urlforTemplates = array();
	
	public static function create() {
		return new NamedRouterRule();
	}
	public function controller($controller) {
		$pair = explode('/', strtolower($controller));
		if (count($pair) != 2) 
			throw new RouterRuleException("NamedRouterRule : Error controller expersion => $controller");
		$this->controller = $pair[0];
		$this->action = $pair[1];
		$this->url('/'.$controller);
		return $this;
	}
	public function default_domain($domain) {
		$this->default_domain = $domain;
		return $this;
	}
	public function domain_url($domain, $url) {
		$this->experssions[] = array('domain' => $domain, 'url' => $url); 
		return $this;
	}
	public function url($url) {
		$this->experssions[] = array('domain' => $this->default_domain, 'url' => $url);
		return $this;
	}
	public function templates($name, $templatepath) {
		$this->templates[$name] = $templatepath;
		return $this;
	}
	public function getIndex() {
		return "{$this->controller}/{$this->action}";
	}
	public function compile() {
		if (empty($this->controller) || empty($this->action))
			throw new RouterRuleException('NamedRouterRule : Must assign property controller and action!');
    	
		if (empty($this->experssions))
			throw new RouterRuleException('NamedRouterRule : Error experssions');
    	
		foreach ($this->experssions as $experssion) {
			$fullExperssion = $experssion['domain'].$experssion['url'];  
			$urlforExperssion['experssion'] = $experssion['url'];
			$urlforExperssion['key']        = array(); 
			$group = null;
			while (preg_match('|<([^:>]+)(?::([^>]+))?>|i', $fullExperssion, $group)) {
				$this->paramNames[] = $group[1];
				$urlforExperssion['experssion'] = preg_replace('|<([^:>]+)(?::([^>]+))?>|i', ':\1', $urlforExperssion['experssion'], 1);
				$urlforExperssion['key'][] = $group[1];
				if (isset($group[2])) {
					$fullExperssion = preg_replace('|<([^:>]+)(?::([^>]+))?>|i', '(?P{\1}\2)', $fullExperssion, 1);
				} else {
					$fullExperssion = preg_replace('|<([^:>]+)(?::([^>]+))?>|i', '(?P{\1}.+)', $fullExperssion, 1);
				}
				unset($group);
			}
			if ($experssion['url'] == '/')
				$fullExperssion .= '?';      
			$fullExperssion = str_replace(array('{', '}'), array('<', '>'), $fullExperssion);
			$this->pattern[] = "|^$fullExperssion$|i";
    		
			$keys = array_unique($urlforExperssion['key']);
			sort($keys);
			if ($experssion['domain'] != ROUTER_DEFAULT_DOMAIN)
				$urlforExperssion['experssion'] = $experssion['domain'].$urlforExperssion['experssion'];
			$this->urlforTemplates[implode(':', $keys)] = $urlforExperssion['experssion'];
		}
    	
		if (isset($this->templates['success']) == false) {
			$this->templates['success'] = "/{$this->controller}/{$this->action}";
		}
		if (isset($this->templates['error']) == false) {
			$this->templates['error'] = '/default/error';
		}
		$this->paramNames = array_unique($this->paramNames);

		return $this;
	}
	public function addto($router) {
		$router->addRule($this);
		return $this;
	}
	public function dispatch($context, $url) {
		$group = null; 
		foreach ($this->pattern as $pattern) {
			if (preg_match($pattern, $url, $group)) {
				$context->controller = $this->controller;
				$context->action = $this->action;
				$this->checkCallable($context->controller, $context->action);
				foreach ($this->paramNames as $paramName) {
					if (isset($group[$paramName])) {
						$context->params[$paramName] = $group[$paramName];
						$context->request->setRequest($paramName, $group[$paramName]);
					}
				}
				$context->templates = $this->templates;
				return true;		
			}
		}
		return false;
	}
	public function urlfor($controller, $params, $oCharset, $iCharset) {
		ksort($params);
		$max_match = 0;
		$matchs_array = array();
		foreach ($this->urlforTemplates as $key => $urlforTemplate) {
			if ($key == "") {
				$matchs_array[0] = $urlforTemplate;
				continue;
			}
			$template_key = explode(':', $key);
			if (array_intersect($template_key, array_keys($params)) == $template_key) {
				$key_count = count($template_key);
				$matchs_array[$key_count] = $urlforTemplate;
				$max_match = max($max_match, $key_count);
			}
		}
		$template = $matchs_array[$max_match];
		foreach ($params as $key => $value) {
			if (strpos($template, ":$key") != false) {
				$template = str_replace(":$key", urldecode($value), $template);
				unset($params[$key]);
			}
		}
		if (empty($params) == false) {
			$template .= '?';
			foreach ($params as $key => $value) {
				$template .= "$key=".urlencode($value)."&";
			}
			$template = substr($template, 0, -1);
		}
		return mb_convert_encoding($template, $oCharset, $iCharset);
	}
}
?>
